#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import Ejs from 'ejs';
import express from 'express';
import favicon from 'serve-favicon';
import serialize from 'serialize-javascript';
import generateConfig from './config';
import { createBundleRenderer } from 'vue-bundle-renderer';
import { SSRContext } from '@vue/server-renderer';
const config = generateConfig();
interface IRenderToString {
  html: any;
  renderResourceHints: () => any;
  renderStyles: () => any;
  renderScripts: () => any;
}
interface IRenderer {
  renderToString(ssrContext: SSRContext): Promise<IRenderToString>;
}

const resolve = (file: string) => path.resolve(__dirname, file);
// express实例
const app = express();
// 是否是生产环境
const isProd = process.env.NODE_ENV === 'production';

// 创建render对象
function createRenderer(bundle: any, options: any) {
  // 创建一个bundleRender对象
  // 这里相当于将webpack与ssr做结合，直接嵌入webpack的通用loader和plugin
  return createBundleRenderer(
    bundle,
    Object.assign(options, {
      // 是否在一个新的上下文中运行
      runInNewContext: false,
      // vue ssr的渲染器
      renderToString: require('@vue/server-renderer').renderToString,
      // bundleRunner
      bundleRunner: require('bundle-runner'),
      // 打包后的地址
      basedir: resolve('./dist'),
      // 公共路径
      publicPath: '/dist/'
    })
  );
}

let renderer: IRenderer | undefined;
let readyPromise: any;
if (isProd) {
  // 生产环境直接读取build后的json文件
  const bundle = require('../dist/vue-ssr-server-bundle.json');
  const clientManifest = require('../dist/vue-ssr-client-manifest.json');
  renderer = createRenderer(bundle, { clientManifest });
} else {
  // 启动通过内存启动服务端和客户端
  readyPromise = require('./build/setup-dev-server')(app, (bundle: any, options: any) => {
    renderer = createRenderer(bundle, options);
  });
}

const serve = (path: string, cache: boolean) =>
  // 开启express今天服务
  express.static(resolve(path), {
    maxAge: cache && isProd ? 1000 * 60 * 60 * 24 * 30 : 0
  });

// 设置favicon图标
app.use(favicon('./src/assets/logo-48.png'));
// 代理dist目录至静态服务
app.use('/dist', serve(isProd ? '../dist' : './dist', true));

// 渲染服务
async function render(req: any, res: any) {
  // 默认错误逻辑处理
  const handleError = (err: any) => {
    res.status(500).send('500 | Internal Server Error');
    console.error(`error during render : ${req.url}`);
    console.error(err);
  };
  // 渲染vuex中的state
  const renderState = (context: any) => {
    // 在上下文中的key
    const contextKey = 'state';
    // state数据挂载到window中的key
    const windowKey = '__INITIAL_STATE__';
    // 将json序列化成javascript
    const state = serialize(context[contextKey]);
    // 自动移除window上的挂载代码
    const autoRemove = ';(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());';
    // 是否启用nonce安全策略
    const nonceAttr = context.nonce ? ' nonce="' + context.nonce + '"' : '';
    // 返回state数据
    return context[contextKey] ? `<script ${nonceAttr}>window.${windowKey} = ${state}${autoRemove}</script>` : '';
  };
  if (!renderer) return;
  // 传递给客户端的上下文
  const context = {
    url: req.url,
    pageConfig: {
      pageName: '测试落地页',
      components: {
        componentName: 'button',
        backgroundImage: 'https://www.baidu.com',
        children: [{
          componentName: 'button',
          backgroundImage: 'https://www.baidu.com'
        }]
      }
    }
  };
  // 页面文件内容
  let page: IRenderToString | undefined;
  try {
    // 使用renderToString渲染出页面文件
    page = await renderer.renderToString(context);
  } catch (err) {
    // 渲染失败时调用统一报错处理
    handleError(err);
  }
  if (!page) return;
  const { renderStyles, renderResourceHints, renderScripts } = page;
  // 非production添加eruda
  let erudaScript = '';
  if (config.runtime.env !== 'production' && isProd) {
    const erudaCDN = '//cdn.bootcdn.net/ajax/libs/eruda/2.4.1/eruda.min.js';
    const erudaDomCDN = '//cdn.jsdelivr.net/npm/eruda-dom@2.0.0';
    erudaScript = `<script src="${erudaCDN}"></script>
      <script src="${erudaDomCDN}"></script>
      <script>
        eruda.init({
          tool: ['console', 'network', 'elements', 'resources', 'snippets', 'sources'],
        });
        eruda.add(erudaDom);
      </script>`;
  }
  const htmlFilePath = path.resolve(__dirname, './public/index.ejs');
  const html = await Ejs.renderFile(htmlFilePath, {
    renderResourceHints: renderResourceHints(),
    renderStyles: renderStyles(),
    renderScripts: renderScripts(),
    renderState: renderState(context),
    html: page.html,
    codemaoConfig: JSON.stringify(config.runtime),
    eruda: erudaScript
  });

  // 非生产环境输出页面方便检查调试
  if (!isProd) {
    fs.writeFile('rendered.html', html, (err) => {
      if (err) {
        throw err;
      }
    });
  }
  // 设置相应头
  res.setHeader('Content-Type', 'text/html');
  // 发送html
  res.send(html);
}

app.get('*', isProd
  ? render
  : async (req, res) => {
    await readyPromise;
    render(req, res);
  });

const port = config.buildtime.port || 5000;
const host = config.buildtime.ip || '127.0.0.1';
app.listen(port, host, () => {
  console.log(`Server started at http://${host}:${port}`);
});
